package com.ctrip.platform.dal.dao.datasource.read;

import com.ctrip.platform.dal.common.enums.SqlType;
import com.ctrip.platform.dal.dao.datasource.read.param.*;
import com.ctrip.platform.dal.dao.helper.SqlUtils;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public class GroupPreparedStatement extends GroupStatement implements PreparedStatement {


    private int autoGeneratedKeys = -1;

    private int[] columnIndexes;

    private String[] columnNames;

    private List<ParamContext> params = new ArrayList<ParamContext>();

    private List<List<ParamContext>> pstBatchedArgs;

    private String sql;

    public GroupPreparedStatement(GroupConnection connection, String sql) {
        super(connection);
        this.sql = sql;
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        checkClosed();
        closeCurrentResultSet();

        Connection conn = this.groupConnection.getRealConnection(sql, false);

        return executeQueryOnConnection(conn, sql);
    }

    private ResultSet executeQueryOnConnection(Connection conn, String sql) throws SQLException {
        PreparedStatement pstmt = createPreparedStatementInternal(conn, sql);
        setParams(pstmt);
        this.currentResultSet = new GroupResultSet(pstmt.executeQuery());

        return this.currentResultSet;
    }

    protected void setParams(PreparedStatement pstmt) throws SQLException {
        for (ParamContext paramContext : params) {
            paramContext.setParam(pstmt);
        }
    }

    @Override
    public int executeUpdate() throws SQLException {
        checkClosed();
        closeCurrentResultSet();

        Connection conn = this.groupConnection.getRealConnection(sql, true);

        updateCount = executeUpdateOnConnection(conn);
        return updateCount;
    }

    private int executeUpdateOnConnection(final Connection conn) throws SQLException {
        PreparedStatement pstmt = createPreparedStatementInternal(conn, sql);
        setParams(pstmt);

        return pstmt.executeUpdate();
    }

    @Override
    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        params.add(new NullParamContext(parameterIndex, new Object[] { sqlType }));
    }

    @Override
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        params.add(new BooleanParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setByte(int parameterIndex, byte x) throws SQLException {
        params.add(new ByteParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        params.add(new ShortParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        params.add(new IntParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        params.add(new LongParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setFloat(int parameterIndex, float x) throws SQLException {
        params.add(new FloatParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setDouble(int parameterIndex, double x) throws SQLException {
        params.add(new DoubleParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        params.add(new BigDecimalParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        params.add(new StringParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        params.add(new ByteArrayParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setDate(int parameterIndex, Date x) throws SQLException {
        params.add(new DateParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setTime(int parameterIndex, Time x) throws SQLException {
        params.add(new TimeParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        params.add(new TimestampParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.add(new AsciiParamContext(parameterIndex, new Object[] { x, length }));
    }

    @Override
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.add(new AsciiParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.add(new BinaryStreamParamContext(parameterIndex, new Object[] { x, length }));
    }

    @Override
    public void clearParameters() throws SQLException {
        params.clear();
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        params.add(new ObjectParamContext(parameterIndex, new Object[] { x, targetSqlType }));
    }

    @Override
    public void setObject(int parameterIndex, Object x) throws SQLException {
        params.add(new ObjectParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public boolean execute() throws SQLException {
        SqlType sqlType = SqlUtils.getSqlType(sql);
        if (sqlType.isQuery()) {
            executeQuery();
            return true;
        } else {
            this.updateCount = executeUpdate();
            return false;
        }
    }

    @Override
    public void addBatch() throws SQLException {
        if (pstBatchedArgs == null) {
            pstBatchedArgs = new ArrayList<List<ParamContext>>();
        }
        List<ParamContext> newArgs = new ArrayList<ParamContext>(params.size());
        newArgs.addAll(params);

        params.clear();

        pstBatchedArgs.add(newArgs);
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
        params.add(new CharacterStreamParamContext(parameterIndex, new Object[] { reader, length }));
    }

    @Override
    public void setRef(int parameterIndex, Ref x) throws SQLException {
        params.add(new RefParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setBlob(int parameterIndex, Blob x) throws SQLException {
        params.add(new BlobParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setClob(int parameterIndex, Clob x) throws SQLException {
        params.add(new ClobParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setArray(int parameterIndex, Array x) throws SQLException {
        params.add(new ArrayParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        throw new UnsupportedOperationException("dal does not support getMetaData");
    }

    public void setColumnIndexes(int[] columnIndexes) {
        this.columnIndexes = columnIndexes;
    }

    public void setColumnNames(String[] columnNames) {
        this.columnNames = columnNames;
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
        params.add(new DateParamContext(parameterIndex, new Object[] { x, cal }));
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
        params.add(new TimeParamContext(parameterIndex, new Object[] { x, cal }));
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
        params.add(new TimestampParamContext(parameterIndex, new Object[] { x, cal }));
    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
        params.add(new NullParamContext(parameterIndex, new Object[] { sqlType, typeName }));
    }

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        params.add(new URLParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        throw new UnsupportedOperationException("dal does not support getParameterMetaData");
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) throws SQLException {
        params.add(new RowIdParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setNString(int parameterIndex, String value) throws SQLException {
        params.add(new NStringParamContext(parameterIndex, new Object[] { value }));
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
        params.add(new NCharacterStreamParamContext(parameterIndex, new Object[] { value, length }));
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {
        params.add(new NClobParamContext(parameterIndex, new Object[] { value }));
    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
        params.add(new ClobParamContext(parameterIndex, new Object[] { reader, length }));
    }

    public void setResultSetHoldability(int resultSetHoldability) {
        this.resultSetHoldability = resultSetHoldability;
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
        params.add(new BlobParamContext(parameterIndex, new Object[] { inputStream, length }));
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
        params.add(new NClobParamContext(parameterIndex, new Object[] { reader, length }));
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
        params.add(new SQLXMLParamContext(parameterIndex, new Object[] { xmlObject }));
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        params.add(new ObjectParamContext(parameterIndex, new Object[] { x, targetSqlType, scaleOrLength }));
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
        params.add(new AsciiParamContext(parameterIndex, new Object[] { x, length }));
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
        params.add(new BinaryStreamParamContext(parameterIndex, new Object[] { x, length }));
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
        params.add(new CharacterStreamParamContext(parameterIndex, new Object[] { reader, length }));
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
        params.add(new AsciiParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
        params.add(new BinaryStreamParamContext(parameterIndex, new Object[] { x }));
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
        params.add(new CharacterStreamParamContext(parameterIndex, new Object[] { reader }));
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
        params.add(new NCharacterStreamParamContext(parameterIndex, new Object[] { value }));
    }

    @Override
    public void setClob(int parameterIndex, Reader reader) throws SQLException {
        params.add(new ClobParamContext(parameterIndex, new Object[] { reader }));
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
        params.add(new BlobParamContext(parameterIndex, new Object[] { inputStream }));
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
        params.add(new NClobParamContext(parameterIndex, new Object[] { reader }));
    }

    @Override
    public ResultSet executeQuery(String sql) throws SQLException {
        checkClosed();
        closeCurrentResultSet();

        Connection conn = this.groupConnection.getRealConnection(sql, false);

        return executeQueryOnConnection(conn, sql);
    }

    @Override
    public int[] executeBatch() throws SQLException {
        try {
            checkClosed();
            closeCurrentResultSet();

            if (pstBatchedArgs == null || pstBatchedArgs.isEmpty()) {
                return new int[0];
            }

            Connection conn = this.groupConnection.getRealConnection(sql, true);

            return executeBatchOnConnection(conn);
        } finally {
            if (pstBatchedArgs != null) {
                pstBatchedArgs.clear();
            }
        }
    }

    private int[] executeBatchOnConnection(Connection conn) throws SQLException {
        PreparedStatement pstmt = createPreparedStatementInternal(conn, sql);

        for (List<ParamContext> tmpParams : pstBatchedArgs) {
            setBatchParams(pstmt, tmpParams);
            pstmt.addBatch();
        }

        return pstmt.executeBatch();
    }

    private void setBatchParams(PreparedStatement pstmt, List<ParamContext> params) throws SQLException {
        for (ParamContext param : params) {
            param.setParam(pstmt);
        }
    }

    private PreparedStatement createPreparedStatementInternal(Connection conn, String sql) throws SQLException {
        PreparedStatement pstmt;
        if (autoGeneratedKeys != -1) {
            pstmt = conn.prepareStatement(sql, autoGeneratedKeys);
        } else if (columnIndexes != null) {
            pstmt = conn.prepareStatement(sql, columnIndexes);
        } else if (columnNames != null) {
            pstmt = conn.prepareStatement(sql, columnNames);
        } else {
            int resultSetHoldability = this.resultSetHoldability;
            if (resultSetHoldability == -1) {
                resultSetHoldability = conn.getHoldability();
            }

            pstmt = conn.prepareStatement(sql, this.resultSetType, this.resultSetConcurrency, resultSetHoldability);
        }

        pstmt.setQueryTimeout(queryTimeout);
        pstmt.setFetchSize(fetchSize);
        pstmt.setMaxRows(maxRows);

        setInnerStatement(pstmt);

        return pstmt;
    }

    public void setAutoGeneratedKeys(int autoGeneratedKeys) {
        this.autoGeneratedKeys = autoGeneratedKeys;
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return null;
    }
}
